package com.csp.app.service;

import  com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.BinaryClient;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPubSub;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * redis操作类
 *
 * @author admin
 */
@Service
public class RedisService {
    private static Logger logger = LoggerFactory.getLogger(RedisService.class);
    @Autowired
    private JedisPool jedisPool;
    private  final String ENCODING = "UTF-8";
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 将一个值插入到列表头部，value可以重复，返回列表的长度 
     * @param key
     * @param value String 
     * @return 返回List的长度
     */
    public  Long lpush(String key, String value){
        Jedis jedis = jedisPool.getResource();
        Long length = jedis.lpush(key, value);
        jedis.close();
        return length;
    }

    /**
     * 将多个值插入到列表头部，value可以重复 
     * @param key
     * @param values String[] 
     * @return 返回List的数量size
     */
    public  Long lpush(String key, String[] values){
        Jedis jedis = jedisPool.getResource();
        Long size = jedis.lpush(key, values);
        jedis.close();
        //System.out.println(result);  
        return size;
    }

    /**
     * 获取List列表 
     * @param key
     * @param start long，开始索引 
     * @param end long， 结束索引 
     * @return List<String>
     */
    public  List<String> lrange(String key, long start, long end){
        Jedis jedis = jedisPool.getResource();
        List<String> list = jedis.lrange(key, start, end);
        jedis.close();
        return list;
    }

    /**
     * 通过索引获取列表中的元素 
     * @param key
     * @param index，索引，0表示最新的一个元素 
     * @return String
     */
    public  String lindex(String key, long index){
        Jedis jedis = jedisPool.getResource();
        String str = jedis.lindex(key, index);
        jedis.close();
        return str;
    }

    /**
     * 获取列表长度，key为空时返回0 
     * @param key
     * @return Long
     */
    public  Long llen(String key){
        Jedis jedis = jedisPool.getResource();
        Long length = jedis.llen(key);
        jedis.close();
        return length;
    }

    /**
     * 在列表的元素前或者后插入元素，返回List的长度 
     * @param key
     * @param where LIST_POSITION 
     * @param pivot 以该元素作为参照物，是在它之前，还是之后（pivot：枢轴;中心点，中枢;[物]支点，支枢;[体]回转运动。） 
     * @param value
     * @return Long
     */
    public  Long linsert(String key, BinaryClient.LIST_POSITION where, String pivot, String value){
        Jedis jedis = jedisPool.getResource();
        Long length = jedis.linsert(key, where, pivot, value);
        jedis.close();
        return length;
    }

    /**
     * 将一个或多个值插入到已存在的列表头部，当成功时，返回List的长度；当不成功（即key不存在时，返回0） 
     * @param key
     * @param value String 
     * @return Long
     */
    public  Long lpushx(String key, String value){
        Jedis jedis = jedisPool.getResource();
        Long length = jedis.lpushx(key, value);
        jedis.close();
        return length;
    }

    /**
     * 将一个或多个值插入到已存在的列表头部，当成功时，返回List的长度；当不成功（即key不存在时，返回0） 
     * @param key
     * @param values String[] 
     * @return Long
     */
    public  Long lpushx(String key, String[] values){
        Jedis jedis = jedisPool.getResource();
        Long length = jedis.lpushx(key, values);
        jedis.close();
        return length;
    }

    /**
     * 移除列表元素，返回移除的元素数量 
     * @param key
     * @param count，标识，表示动作或者查找方向 
     * <li>当count=0时，移除所有匹配的元素；</li> 
     * <li>当count为负数时，移除方向是从尾到头；</li> 
     * <li>当count为正数时，移除方向是从头到尾；</li> 
     * @param value 匹配的元素 
     * @return Long
     */
    public  Long lrem(String key, long count, String value){
        Jedis jedis = jedisPool.getResource();
        Long length = jedis.lrem(key, count, value);
        jedis.close();
        return length;
    }


    /**
     * 移出并获取列表的第一个元素，当列表不存在或者为空时，返回Null 
     * @param key
     * @return String
     */
    public  String lpop(String key){
        Jedis jedis = jedisPool.getResource();
        String value = jedis.lpop(key);
        jedis.close();
        return value;
    }
    public <T> List<T>  lpopObjects(String key,Class<T> tClass){
        List<T> list = new ArrayList<>();
        String item = lpop(key);
        while (StringUtils.isNotEmpty(item)){
            T t = JSON.parseObject(item, tClass);
            list.add(t);
            item = lpop(key);
        }
        return list;
    }
    /**
     * 移除并获取列表最后一个元素，当列表不存在或者为空时，返回Null 
     * @param key
     * @return String
     */
    public  String rpop(String key){
        Jedis jedis = jedisPool.getResource();
        String value = jedis.rpop(key);
        jedis.close();
        return value;
    }

    /**
     * 在列表中的尾部添加一个个值，返回列表的长度 
     * @param key
     * @param value
     * @return Long
     */
    public  Long rpush(String key, String value){
        Jedis jedis = jedisPool.getResource();
        Long length = jedis.rpush(key, value);
        jedis.close();
        return length;
    }
    /**
     * 在列表中的尾部添加一个个值，返回列表的长度
     * @param key
     * @param o
     * @return Long
     */
    public Long rpushObject(String key,Object o){
        return rpush(key,JSON.toJSONString(o));
    }
    /**
     * 在列表中的尾部添加多个值，返回列表的长度 
     * @param key
     * @param values
     * @return Long
     */
    public  Long rpush(String key, String[] values){
        Jedis jedis = jedisPool.getResource();
        Long length = jedis.rpush(key, values);
        jedis.close();
        return length;
    }

    /**
     * 仅当列表存在时，才会向列表中的尾部添加一个值，返回列表的长度 
     * @param key
     * @param value
     * @return Long
     */
    public  Long rpushx(String key, String value){
        Jedis jedis = jedisPool.getResource();
        Long length = jedis.rpushx(key, value);
        jedis.close();
        return length;
    }

    /**
     * 移除列表的最后一个元素，并将该元素添加到另一个列表并返回 
     * @param sourceKey 源列表的key，当源key不存在时，结果返回Null 
     * @param targetKey 目标列表的key，当目标key不存在时，会自动创建新的 
     * @return String
     */
    public  String rpopLpush(String sourceKey, String targetKey){
        Jedis jedis = jedisPool.getResource();
        String value = jedis.rpoplpush(sourceKey, targetKey);
        jedis.close();
        return value;
    }

    /**
     * 移出并获取列表的【第一个元素】， 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 
     * @param timeout 单位为秒 
     * @param keys
     * <li>当有多个key时，只要某个key值的列表有内容，即马上返回，不再阻塞。</li> 
     * <li>当所有key都没有内容或不存在时，则会阻塞，直到有值返回或者超时。</li> 
     * <li>当超期时间到达时，keys列表仍然没有内容，则返回Null</li> 
     * @return List<String>
     */
    public  List<String> blpop(int timeout, String... keys){
        Jedis jedis = jedisPool.getResource();
        List<String> values = jedis.blpop(timeout, keys);
        jedis.close();
        return values;
    }

    /**
     * 移出并获取列表的【最后一个元素】， 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 
     * @param timeout 单位为秒 
     * @param keys
     * <li>当有多个key时，只要某个key值的列表有内容，即马上返回，不再阻塞。</li> 
     * <li>当所有key都没有内容或不存在时，则会阻塞，直到有值返回或者超时。</li> 
     * <li>当超期时间到达时，keys列表仍然没有内容，则返回Null</li> 
     * @return List<String>
     */
    public  List<String> brpop(int timeout, String... keys){
        Jedis jedis = jedisPool.getResource();
        List<String> values = jedis.brpop(timeout, keys);
        jedis.close();
        return values;
    }

    /**
     * 从列表中弹出列表最后一个值，将弹出的元素插入到另外一个列表中并返回它；  
     * 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 
     * @param sourceKey 源列表的key，当源key不存在时，则会进行阻塞 
     * @param targetKey 目标列表的key，当目标key不存在时，会自动创建新的 
     * @param timeout 单位为秒 
     * @return String
     */
    public  String brpopLpush(String sourceKey, String targetKey, int timeout){
        Jedis jedis = jedisPool.getResource();
        String value = jedis.brpoplpush(sourceKey, targetKey, timeout);
        jedis.close();
        return value;
    }
    public long incrUntil(String key, int db) {
        RedisConnection connection = getConnection();
        Long incr = 0L;
        try {
            if (connection != null) {
                connection.select(db);
                incr = connection.incr(key.getBytes(ENCODING));
                if (incr > 10000000) {
                    set(key, 0, db);
                }
            }
        } catch (Exception e) {
            logger.error("redis incr error:{}", e);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return incr;
    }

    public void set(String key, Object value, int db) {
        RedisConnection connection = getConnection();
        try {
            if (connection != null) {
                connection.select(db);
                connection.set(key.getBytes(ENCODING), value.toString().getBytes(ENCODING));
            }
        } catch (Exception e) {
            logger.error("redis set error:{}", e);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }

    /**
     * set Object
     *
     * @param key
     * @param value
     * @param db
     * @throws Exception
     */
    public void setObject(String key, Object value, int db) {
        RedisConnection connection = getConnection();
        try {
            if (connection != null) {
                connection.select(db);
                connection.set(key.getBytes(ENCODING), JSON.toJSONString(value).getBytes(ENCODING));
            }
        } catch (Exception e) {
            logger.error("redis setObject error:{}", e);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }

    /**
     * delete key
     *
     * @param key
     * @param db
     */
    public void delete(String key, int db) {
        RedisConnection connection = getConnection();
        try {
            if (connection != null) {
                connection.select(db);
                connection.del(key.getBytes(ENCODING));
            }
        } catch (Exception e) {
            logger.error("redis del error:{}", e);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }

    /**
     * 获取String类型的value
     *
     * @param key
     * @param db
     * @return
     */
    public String getString(String key, int db) {
        RedisConnection connection = getConnection();
        byte[] bytes = null;
        try {
            if (connection != null) {
                connection.select(db);
                bytes = connection.get(key.getBytes(ENCODING));
            }
            return bytes == null ? null : new String(bytes, ENCODING);
        } catch (Exception e) {
            logger.error("redis get error:{}", e);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return null;
    }

    /**
     * 获取Object
     *
     * @param key
     * @param db
     * @param tClass
     * @param <T>
     * @return
     * @throws Exception
     */
    public <T> T getObject(String key, int db, Class<T> tClass) {
        RedisConnection connection = getConnection();
        byte[] bytes = null;
        try {
            if (connection != null) {
                connection.select(db);
                bytes = connection.get(key.getBytes(ENCODING));
            }
            String jsonStr;
            if (bytes != null) {
                jsonStr = new String(bytes, ENCODING);
                return JSON.parseObject(jsonStr, tClass);
            }
        } catch (Exception e) {
            logger.error("redis getObject error:{}", e);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return null;
    }

    public Long incr(String key, int db) {
        RedisConnection connection = getConnection();
        Long incr = 0L;
        try {
            if (connection != null) {
                connection.select(db);
                incr = connection.incr(key.getBytes(ENCODING));
            }
        } catch (Exception e) {
            logger.error("redis incr error:{}", e);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return incr;
    }


    public void setEx(String key, Integer second, String value, int db) {
        RedisConnection connection = getConnection();
        try {
            if (connection != null) {
                connection.select(db);
                connection.setEx(key.getBytes(ENCODING), second, value.getBytes(ENCODING));
            }
        } catch (Exception e) {
            logger.error("redis setEx error:{}", e);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }

    public Long ttl(String key, int db) {
        RedisConnection connection = getConnection();
        Long ttl = (long) -1;
        try {
            if (connection != null) {
                connection.select(db);
                ttl = connection.ttl(key.getBytes(ENCODING));
            }
        } catch (Exception e) {
            logger.error("redis ttl error:{}", e);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return ttl;
    }

    private RedisConnection getConnection() {
        return this.redisTemplate.getConnectionFactory().getConnection();
    }

    public boolean hmset(String key, Map<String, Object> map, int db) {

        RedisConnection connection = null;
        try {
            connection = getConnection();
            connection.select(db);
            Map<byte[], byte[]> tempMap = new HashMap<>();
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                tempMap.put(entry.getKey().getBytes(ENCODING), JSON.toJSONString(entry.getValue()).getBytes(ENCODING));
            }
            connection.hMSet(key.getBytes(ENCODING), tempMap);
        } catch (UnsupportedEncodingException e) {
            logger.error("hmset error:{}", e);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return true;
    }

    public boolean hset(String key, String field, Object value, int db) {

        RedisConnection connection = null;
        try {
            connection = getConnection();
            connection.select(db);
            connection.hSet(key.getBytes(ENCODING), field.getBytes(ENCODING), JSON.toJSONString(value).getBytes(ENCODING));
        } catch (UnsupportedEncodingException e) {
            logger.error("hset error:{}", e);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return true;
    }

    public String hget(String key, String field, int db) {
        RedisConnection connection = null;
        try {
            connection = getConnection();
            connection.select(db);
            byte[] bytes = connection.hGet(key.getBytes(ENCODING), field.getBytes(ENCODING));
            if (bytes != null) {
                return new String(bytes, ENCODING);
            }
        } catch (UnsupportedEncodingException e) {
            logger.error("hget error:{}", e);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return null;
    }

    /**
     * 订阅多个频道
     *
     * @param jedisPubSub
     * @param channelName
     * @return
     */
    public boolean subscribe(JedisPubSub jedisPubSub, String... channelName) {
        Jedis jedis = this.jedisPool.getResource();
        try {
            jedis.subscribe(jedisPubSub, channelName);
            return true;
        } catch (Exception e) {
            logger.error("订阅频道异常:{}", e);
        } finally {
            jedis.close();
        }
        return false;
    }

    /**
     * 发布消息
     *
     * @param channelName
     * @param msg
     * @return
     */
    public Long publish(String channelName, String msg) {
        Long result = -1L;
        Jedis jedis = this.jedisPool.getResource();
        try {
            result = jedis.publish(channelName, msg);
        } catch (Exception e) {
            logger.error("发布消息异常:{}", e);
        } finally {
            jedis.close();
        }
        return result;
    }

    /**
     * 发布消息
     *
     * @param channel
     * @param message
     */
    public void convertAndSend(String channel, Object message) {
        if (message instanceof String) {
            redisTemplate.convertAndSend(channel, ((String) message));
        } else {
            redisTemplate.convertAndSend(channel, JSON.toJSON(message));
        }
    }

    /**
     * 清空某库
     *
     * @param index
     */
    public void flushDB(int index) {
        RedisConnection connection = getConnection();
        connection.select(index);
        connection.flushDb();
    }
}
